namespace Core
{
    using System;
    using System.Collections.Generic;
    using System.Linq.Expressions;
    using System.Reflection;
    using Contracts.Interfaces;
    using System.Linq;
    using Contracts.Enums;

    /// <summary>
    /// Extension methods
    /// </summary>
    public static class ExtensionMethods
    {
        /// <summary>
        /// Converts an expression into a <see cref="MemberInfo"/>.
        /// </summary>
        /// <param name="expression">The expression to convert.</param>
        /// <returns>The member info.</returns>
        internal static MemberInfo GetMemberInfo(this Expression expression)
        {
            var lambda = (LambdaExpression)expression;
            MemberExpression memberExpression;

            if (lambda.Body is UnaryExpression)
            {
                var unaryExpression = (UnaryExpression)lambda.Body;
                memberExpression = (MemberExpression)unaryExpression.Operand;
            }
            else memberExpression = (MemberExpression)lambda.Body;

            return memberExpression.Member;
        }
        /// <summary>
        /// Enumerate features
        /// </summary>
        /// <param name="features"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        internal static IEnumerable<IFeature<object>> Features(this IFeatures features, Predicate<IFeature<object>> predicate = null)
        {
            if(features.HasFeatures)
                using(var enu = features.Features.GetEnumerator())
                {
                    while (enu.MoveNext())
                    {
                        if(predicate ==null)
                            yield return enu.Current;
                        else if(predicate(enu.Current))
                            yield return enu.Current;
                    }
                }
        }
        /// <summary>
        /// Throws a ObjectDisposedException exception if the instance has been disposed
        /// </summary>
        /// <param name="disposable"></param>
        internal static void ThrowIfDisposed(this IIsDisposed disposable)
        {
            if(disposable.IsDisposed)
                throw new ObjectDisposedException("The instance has been disposed");
        }
        /// <summary>
        /// Sums the agent extensions modifiers values
        /// </summary>
        /// <param name="extensions">An IEnumerable of IExtension</param>
        /// <param name="featureType">A FeatureTypesEnum value</param>
        /// <param name="feature">A FeaturesEnum value</param>
        /// <returns>The sum of modifiers value</returns>
        internal static double SumModifiersValues(this IEnumerable<IExtension> extensions, FeatureTypesEnum featureType, FeaturesEnum feature)
        {
            return  (from extension in extensions.Where(e => e.Modifiers != null && e.Modifiers.Any())
                    let mods = extension.Modifiers.Where(m => Equals(m.ModifiedFeatureType,featureType) && Equals(m.Feature, feature))
                    where mods.Any()
                    let current = extension
                    select mods.Sum(modifier => modifier.Value * current.Level)).Sum();
        }

        internal static double SumModifiersValues(this IEnumerable<IModule> modules, FeatureTypesEnum featureType, FeaturesEnum feature)
        {
            return modules
                .Where(m => m.Modifiers != null && m.Modifiers.Any())
                .Select(module => module.Modifiers
                    .Where(m => Equals(m.ModifiedFeatureType, featureType) && Equals(m.Feature, feature))
                    .FirstOrDefault())
                .Where(mod => mod != null).Sum(mod => mod.Value);
        }

        internal static double SumModifiersValues(this IEnumerable<IModule> modules, FeatureTypesEnum featureType, FeaturesEnum feature, ModuleGroupsEnum moduleGroup)
        {
            return modules
                .Where(m => Equals(m.Group, moduleGroup) && m.Modifiers != null && m.Modifiers.Any())
                .Select(module => module.Modifiers
                    .Where(m => Equals(m.ModifiedFeatureType, featureType) && Equals(m.Feature, feature) && (module.ActivationType == ModuleActivationTypesEnum.Passive || (module.ActivationType == ModuleActivationTypesEnum.Active && module.IsActive)))
                    .FirstOrDefault())
                    .Where(mod => mod != null).Sum(mod => mod.Value);
        }

        internal static double SumModifiersValues(this IEnumerable<IModule> modules, FeatureTypesEnum featureType, FeaturesEnum feature, ModuleMetaGroupsEnum moduleMetaGroup)
        {
            return modules
                .Where(m => Equals(m.MetaGroup, moduleMetaGroup) && m.Modifiers != null && m.Modifiers.Any())
                .Select(module => module.Modifiers
                    .Where(m => Equals(m.ModifiedFeatureType, featureType) && Equals(m.Feature, feature))
                    .FirstOrDefault())
                .Where(mod => mod != null).Sum(mod => mod.Value);
        }

        internal static double SumModifiersValues(this IEnumerable<IModule> modules, FeatureTypesEnum featureType, FeaturesEnum feature, ModuleMetaGroupsEnum moduleMetaGroup, ModuleGroupsEnum moduleGroup)
        {
            return modules
                .Where(m => Equals(m.MetaGroup, moduleMetaGroup) && Equals(m.Group,moduleGroup) && m.Modifiers != null && m.Modifiers.Any())
                .Select(module => module.Modifiers
                    .Where(m => Equals(m.ModifiedFeatureType, featureType) && Equals(m.Feature, feature))
                    .FirstOrDefault())
                .Where(mod => mod != null).Sum(mod => mod.Value);
        }

        internal static double SumModifiersValues(this IEnumerable<IBonusModifier> modifiers, IEnumerable<IExtension> extensions, FeatureTypesEnum featureType, FeaturesEnum feature)
        {
            return (from bmod in modifiers.Where(m => Equals(m.ModifiedFeatureType, featureType) && Equals(m.Feature, feature))
                    let bextension = extensions.Where(e => Equals(bmod.Extension, e)).FirstOrDefault()
                    where bextension != null
                    select bmod.Value*bextension.Level).Sum();
        }
        
        internal static double Aggregate(this IEnumerable<IModule> modules, double value, Func<double,IModifier, double> function , FeatureTypesEnum featureType, FeaturesEnum feature)
        {
            return modules
                .Where(m => m.Modifiers != null && m.Modifiers.Any())
                .Select(module => module.Modifiers
                    .Where(m => Equals(m.ModifiedFeatureType, featureType) && Equals(m.Feature, feature))
                    .FirstOrDefault())
                .Where(mod => mod != null)
                .Aggregate(value, function);
        }

        internal static double Aggregate(this IEnumerable<IModule> modules, double value, Func<double, IModifier, double> function, FeatureTypesEnum featureType, FeaturesEnum feature, ModuleGroupsEnum moduleGroup)
        {
            return modules
                .Where(m => m.Group == moduleGroup && m.Modifiers != null && m.Modifiers.Any())
                .Select(module => module.Modifiers
                    .Where(m => Equals(m.ModifiedFeatureType, featureType) && Equals(m.Feature, feature))
                    .FirstOrDefault())
                .Where(mod => mod != null)
                .Aggregate(value, function);
        }

        internal static double Aggregate(this IEnumerable<IModule> modules, double value, Func<double, IModifier, double> function, FeatureTypesEnum featureType, FeaturesEnum feature, ModuleMetaGroupsEnum moduleMetaGroup)
        {
            return modules
                .Where(m => m.MetaGroup == moduleMetaGroup && m.Modifiers != null && m.Modifiers.Any())
                .Select(module => module.Modifiers
                    .Where(m => Equals(m.ModifiedFeatureType, featureType) && Equals(m.Feature, feature))
                    .FirstOrDefault())
                .Where(mod => mod != null)
                .Aggregate(value, function);
        }

        internal static double Aggregate(this IEnumerable<IModule> modules, double value, Func<double, IModifier, double> function, FeatureTypesEnum featureType, FeaturesEnum feature, ModuleMetaGroupsEnum moduleMetaGroup, ModuleGroupsEnum moduleGroup)
        {
            return modules
                .Where(m => m.MetaGroup == moduleMetaGroup && m.Group == moduleGroup && m.Modifiers != null && m.Modifiers.Any())
                .Select(module => module.Modifiers
                    .Where(m => Equals(m.ModifiedFeatureType, featureType) && Equals(m.Feature, feature))
                    .FirstOrDefault())
                .Where(mod => mod != null)
                .Aggregate(value, function);
        }
    }
}